package main

import (
    goctp "gitee.com/mayiweb/goctp_sopt"
    "gitee.com/mayiweb/goctp_sopt/safe"
    "gitee.com/mayiweb/goctp_sopt/ctptest"
    "os"
    "fmt"
    "log"
)

var (
    // ctp 句柄及配置项
    Ctp CtpClient

    // 行情模块函数 句柄
    MdSpi FtdcMdSpi

    // 交易模块函数 句柄
    TraderSpi FtdcTradeSpi

    // CtpTest 回测模块 句柄
    CtpTestMdApi *ctptest.MdApi
    CtpTestTradeApi *ctptest.TradeApi

    // 交易所合约详情列表 InstrumentInfoStruct
    MapInstrumentInfos safe.Map

    // 报单列表（已成交、未成交、撤单等状态）的列表数据 OrderListStruct
    MapOrderList safe.Map

    // ctp 服务器，及交易账号
    MdFront []string
    TraderFront []string

    BrokerID string
    InvestorID string
    Password string

    // 客户端认证
    AppID string
    AuthCode string

    // ctp 流文件，绝对路径
    StreamFile string = GetCurrentDirectory() + "/StreamFile/"

    // 买卖方向：买
    OrderBuy byte  = '0'

    // 买卖方向：卖
    OrderSell byte = '1'

    // 运行模式（prod 生产，test 标准环境测试，dev 24小时测试）
    RunMode string

    // 回测模式
    BackTestingMode string = "backTesting"
)


// Ctp 行情 spi 回调函数
type FtdcMdSpi struct{
    CtpClient
}

// Ctp 交易 spi 回调函数
type FtdcTradeSpi struct{
    CtpClient
}

// Ctp 客户端 行情、交易模块 全局变量
type CtpClient struct {

    // 行情模块 api
    MdApi goctp.CThostFtdcMdApi

    // 交易模块 api
    TradeApi goctp.CThostFtdcTraderApi

    BrokerID    string
    InvestorID  string
    Password    string

    // 客户端认证
    AppID       string
    AuthCode    string

    // 当前交易日期
    TradingDay string

    // 当前交易月份
    TradeMonth string

    // 行情请求编号
    MdRequestId int

    // 交易请求编号
    TradeRequestId int

    // 交易系统是否已经初始化了
    IsTradeInit bool

    // 交易程序是否初始化完成（自动完成如下动作：交易账号登陆、结算单确认、查询合约、查询资金账户、查询用户报单、查询用户持仓 后算完成）
    IsTradeInitFinish bool

    // 交易程序是否已登录过
    IsTradeLogin bool

    // 行情程序是否已登录过
    IsMdLogin bool
}

// 设置交易账号
func SetTradeAccount() {

    switch RunMode {

        // 生产环境
        case "prod":
            MdFront     = []string{}
            TraderFront = []string{}
            BrokerID    = ""
            InvestorID  = ""
            Password    = ""
            AppID       = ""
            AuthCode    = ""

        // 测试环境 (与实际生产环境保持一致)
        case "test":
            MdFront     = []string{}
            TraderFront = []string{}
            BrokerID    = ""
            InvestorID  = ""
            Password    = ""
            AppID       = ""
            AuthCode    = ""

        // 7*24 服务器
        case "dev":
            MdFront     = []string{}
            TraderFront = []string{}
            BrokerID    = ""
            InvestorID  = ""
            Password    = ""
            AppID       = ""
            AuthCode    = ""

        // 回测模式
        case BackTestingMode:
            MdFront     = []string{}
            TraderFront = []string{}
            BrokerID    = "8888"
            InvestorID  = "10001"
            Password    = ""
            AppID       = ""
            AuthCode    = ""

        default:
            Println("该模式未设置交易账号信息")
            os.Exit(1)
    }
}

func init() {
    // 全局 行情、交易 函数句柄
    MdSpi     = FtdcMdSpi{}
    TraderSpi = FtdcTradeSpi{}

    CtpTestMdApi    = new(ctptest.MdApi)
    CtpTestTradeApi = new(ctptest.TradeApi)
}

func main() {

    // 运行模式【运行程序时带上参数可设置】
    if len(os.Args) < 2 {
        RunMode = "test"
    } else {
        RunMode = os.Args[1]
    }

    // 设置交易账号
    SetTradeAccount()

    log.Println("启动交易程序")

    // 检查流文件目录是否存在
    fileExists, _ := PathExists(StreamFile)
    if !fileExists {
        err := os.Mkdir(StreamFile, os.ModePerm)
        if err != nil {
           fmt.Println("创建目录失败，请检查是否有操作权限")
        }
    }

    Ctp = CtpClient {
        BrokerID: BrokerID,
        InvestorID: InvestorID,
        Password: Password,
        AppID: AppID,
        AuthCode: AuthCode,
        MdRequestId: 1,
        TradeRequestId: 1,
        IsTradeInit: false,
        IsTradeInitFinish: false,
        IsMdLogin: false,
        IsTradeLogin: false,
    }

    // 回测模式
    if RunMode == BackTestingMode {

        LogPrintln("进入回测模式")

        /*
            // 重置 ctptest 数据，相当于重新初始化（所有测试数据会被清空）
            CtpTestTradeApi.Reset()

            // 在第一笔 tick 之前设置，跨日期回测也需要设置
            // 重置交易日回测环境（还原一些数据，便于跨日期测试）
            sInstrument := ctptest.InstrumentStruct{
                ExchangeID: mInstrument.ExchangeID,
                InstrumentID: mInstrument.InstrumentID,
                VolumeMultiple: mInstrument.VolumeMultiple,
            }

            // 设置测试的合约参数
            CtpTestTradeApi.SetInstrument(sInstrument)

            // 重置当前交易日（跨日期回测需要在 tick 传递前设置，每个交易日都要设置）
            CtpTestTradeApi.SetTradingDay(StringToInt(Ctp.TradingDay))

            sTick := ctptest.TickStruct{
                TradingDay: Input.TestingTick.TradingDay,
                InstrumentID: Input.TestingTick.InstrumentID,
                UpdateTime: Input.TestingTick.UpdateTime,
                LastPrice: Input.TestingTick.LastPrice,
                BidPrice1: Input.TestingTick.BidPrice1,
                AskPrice1: Input.TestingTick.AskPrice1,
            }

            // 开仓前将当前 tick 数据传给 ctptest（在调用 Ctp.TradeApi.ReqOrderInsert 之前设置，便于设置成交时间与价格，使用对手价成交）
            CtpTestTradeApi.SetOrderRefTick(iRequestID, sTick)
         */

        Ctp.MdApi    = CtpTestMdApi
        Ctp.TradeApi = CtpTestTradeApi

    } else {
        Ctp.MdApi    = goctp.CThostFtdcMdApiCreateFtdcMdApi(StreamFile)
        Ctp.TradeApi = goctp.CThostFtdcTraderApiCreateFtdcTraderApi(StreamFile)
    }

    Ctp.MdApi.RegisterSpi(goctp.NewDirectorCThostFtdcMdSpi(&FtdcMdSpi{Ctp}))

    for _, val := range MdFront {
        Ctp.MdApi.RegisterFront(val)
    }
    Ctp.MdApi.Init()


    Ctp.TradeApi.RegisterSpi(goctp.NewDirectorCThostFtdcTraderSpi(&FtdcTradeSpi{Ctp}))

    for _, val := range TraderFront {
        Ctp.TradeApi.RegisterFront(val)
    }

    Ctp.TradeApi.SubscribePublicTopic(goctp.THOST_TERT_QUICK);
    Ctp.TradeApi.SubscribePrivateTopic(goctp.THOST_TERT_QUICK);
    Ctp.TradeApi.Init()
    Ctp.TradeApi.Join()

    // .Join() 如果后面有其它需要处理的功能可以不写，但必须保证程序不能退出，Join 就是保证程序不退出的
}